#include <wx/listimpl.cpp>
#include <wx/bitmap.h>

#include "ChartCtrl.h"

BEGIN_EVENT_TABLE(wxChartCtrl, wxScrolledWindow)
  EVT_SIZE    (wxChartCtrl::OnSize)
END_EVENT_TABLE()

WX_DEFINE_LIST(wxCurveData);
WX_DEFINE_LIST(wxCurvesList);

wxChartCtrl::wxChartCtrl(wxWindow *parent, const wxPoint& pos, const wxSize& size)
: wxScrolledWindow(parent,wxID_ANY,pos,size)
{
	this->SetBackgroundColour(*wxWHITE);
	m_Style = 0;
	m_Prescale = wxVertex2D(0.5f,0.5f);
	m_Unit = wxVertex2D(1,1);
	m_Offset = wxVertex2D(0,0);	

	m_Curves.DeleteContents(true);
	m_LastUpdateTime = GetTickCount();
	m_Refresh = false;
}

wxChartCtrl::~wxChartCtrl(void)
{
	Clear();
}

void wxChartCtrl::OnDraw(wxDC& dc)
{			
	DrawArea(dc,m_AreaStart,m_AreaSize);
	DrawAreaGrid(dc,m_AreaStart,m_AreaSize,m_Prescale,m_Offset,m_Unit);
	DrawLabels(dc);
	
	for(size_t i = 0; i < m_Curves.GetCount();i++)	
		UpdateCurveDraw(i);
}

void wxChartCtrl::OnSize(wxSizeEvent& event)
{
	m_Refresh = true;
	//PrescaleOnResize();
	CalcCoords();
	//this->Refresh();
}

void wxChartCtrl::DrawArea(wxDC& dc, wxPoint &pos, wxSize &size)
{
	dc.SetPen(*wxBLACK_PEN);
	dc.DrawRectangle(pos,size);
}

void wxChartCtrl::DrawAreaGrid(wxDC& dc, wxPoint &pos, wxSize &size, wxVertex2D &prescal, wxVertex2D &offset, wxVertex2D &unit)
{
	wxVertex2D begin;
	wxVertex2D prn;

	
	if(offset.x == 0)
		begin.x = 0;
	else if(offset.x > 0)
		begin.x = unit.x - fmod(offset.x,unit.x);
	else
		begin.x = abs( fmod(offset.x,unit.x) );

	if(offset.y == 0)
		begin.y = 0;
	else if(offset.y > 0)
		begin.y = unit.y - fmod(offset.y,unit.y);
	else
		begin.y = abs( fmod(offset.y,unit.y) );


	wxPoint point = GetPrescPointInv(begin);	
	wxPoint unitPoint = GetPresc(m_Unit);
	wxPoint tmp;
	prn = offset;
	prn.x += prn.x > 0? begin.x : abs(begin.x);
	prn.y += prn.y > 0? begin.y : abs(begin.y);

	//dc.SetPen(*wxGREY_PEN);
	dc.SetPen(wxPen(wxColor(_("LIGHT GREY"))));

	//todo: do not draw all verticles

	if(unitPoint.y > 10)
	{
		float ilen =  begin.y + GetRealAreaSize().y;
		for(float i = begin.y; i < ilen ; i += m_Unit.y)
		{
			tmp = GetPrescPointInv(begin);

			if(tmp.y > m_AreaStart.y + m_AreaSize.GetHeight())
				break;

			dc.DrawLine(m_AreaStart.x,tmp.y,m_AreaStart.x + m_AreaSize.GetWidth(),tmp.y);

			if(unitPoint.y > 40 && point.y != tmp.y )
				DrawUnits(dc,wxPoint(m_AreaStart.x,tmp.y),prn,false);
			begin.y += m_Unit.y;
			prn.y += m_Unit.y;
		}
	}

	if(unitPoint.x > 10)
	{
		float jlen =  begin.x + GetRealAreaSize().x;
		for(float j = begin.x; j < jlen ;j += m_Unit.x)
		{			
			tmp = GetPrescPointInv(begin);

			if(tmp.x > m_AreaStart.x + m_AreaSize.GetWidth())
				break;

			dc.DrawLine(tmp.x,m_AreaStart.y,tmp.x,m_AreaStart.y + m_AreaSize.GetHeight() );

			if(unitPoint.x > 40)
				DrawUnits(dc,wxPoint(tmp.x,m_AreaStart.y + m_AreaSize.GetHeight()),prn);
			begin.x += m_Unit.x;
			prn.x += m_Unit.x;
		}
	}

}

void wxChartCtrl::DrawLabels(wxDC& dc)
{
	dc.SetFont(*wxNORMAL_FONT);
	wxSize size;

	if(m_Style & wxSHOW_X_LABEL)
	{
		size = dc.GetTextExtent(m_XLabel);
		if(size.GetWidth() < m_AreaSize.GetWidth())
		{
			dc.DrawText(m_XLabel,wxPoint(m_AreaStart.x + 
				((m_AreaSize.GetWidth() - size.GetWidth())/2) + 2
				,m_AreaStart.y + m_AreaSize.GetHeight() + AREA_BORDER + 2));
		}
	}
	if(m_Style & wxSHOW_Y_LABEL)
	{
		size = dc.GetTextExtent(m_YLabel);
		if(size.GetWidth() < m_AreaSize.GetHeight())
		{
			dc.DrawRotatedText(m_YLabel,wxPoint(m_AreaStart.x - size.GetHeight() - AREA_BORDER - 4,
				((m_AreaSize.GetHeight() + size.GetWidth())/2) + AREA_BORDER),90);
		}
	}
}

void wxChartCtrl::DrawUnits(wxDC& dc, wxPoint &pos, wxVertex2D &vert,bool horizontal)
{
	dc.SetFont(*wxSMALL_FONT);
	dc.DrawCircle(pos,4);

	if(horizontal && (m_Style & wxSHOW_X_UNITS))
		dc.DrawText(wxString::Format(_("%.4f"),vert.x),pos - wxPoint(13,-3));
	else if(!horizontal && (m_Style & wxSHOW_Y_UNITS))
		dc.DrawRotatedText(wxString::Format(_("%.4f"),vert.y),pos - wxPoint(13,-13),90);
}

void wxChartCtrl::UpdateCurveDraw(size_t index, size_t count)
{
	wxCurveItem *data = m_Curves.Item(index)->GetData();
	wxPoint point;
	wxClientDC dc(this);
	dc.SetClippingRegion(m_AreaStart,m_AreaSize);

	wxPoint prev;

	dc.SetBrush(*wxRED_BRUSH);
	dc.SetPen(data->CurvePen);

	if(count == (size_t)-1)
		count = data->CurveList.GetCount();		

	//optimalization - start from end
	//for(size_t i = 0; i < data->GetCount();i++)
	//{
	bool first = true;
	for(size_t i = data->CurveList.GetCount(); i > data->CurveList.GetCount() - count ;)
	{
		i--;
		wxVertex2D vertex = *data->CurveList.Item(i)->GetData();
		point = GetPrescPointInvOff(vertex);						
		if(!first)
		{			
			if(prev == point)
				continue;
			dc.DrawLine(prev,point);
			if(prev.x < m_AreaStart.x)
				break;//don`t draw verts out of border
		}
		else
			first = false;

		//dc.DrawCircle(point,2);	
		prev = point;
	}
	data->UpdItems = 0;
}

void wxChartCtrl::CalcCoords()
{
	m_AreaStart = wxPoint(0,0);
	m_AreaSize = GetSize();

	m_AreaStart += wxPoint(AREA_BORDER,AREA_BORDER);
	m_AreaSize -= wxSize(2*AREA_BORDER,2*AREA_BORDER);

	if(m_Style & wxSHOW_X_LABEL)
	{
		m_AreaSize.y -= LABEL_FONT_HEIGHT;	
	}

	if(m_Style & wxSHOW_Y_LABEL)
	{
		m_AreaStart.x += LABEL_FONT_HEIGHT;
		m_AreaSize.x -= LABEL_FONT_HEIGHT;
	}
}

wxPoint wxChartCtrl::GetPresc(wxVertex2D &vertex)
{
	wxPoint point;

	point.x = vertex.x * m_Prescale.x * PRESCALER_CONST/*m_AreaSize.GetWidth()*/;
	point.y = vertex.y * m_Prescale.y * PRESCALER_CONST/*m_AreaSize.GetHeight()*/;

	return point;
}

wxPoint wxChartCtrl::GetPrescPoint(wxVertex2D &vertex)
{
	wxPoint point;

	point = GetPresc(vertex);

	point += m_AreaStart;

	return point;
}



wxPoint wxChartCtrl::GetPrescPointInv(wxVertex2D &vertex)
{
	wxPoint point;

	point = GetPrescPoint(vertex);
	//point.x -= (2*AREA_BORDER);
	point.y = ( m_AreaSize.GetHeight() - point.y) + (2*AREA_BORDER);
	
	return point;
}

wxPoint wxChartCtrl::GetPrescPointInvOff(wxVertex2D vertex)
{
	wxPoint point;

	vertex.x -= m_Offset.x;
	vertex.y -= m_Offset.y;

	point = GetPrescPointInv(vertex);

	return point;
}

wxVertex2D wxChartCtrl::GetRealAreaSize()
{
	return GetRealFromPoint(wxPoint(m_AreaSize.GetWidth(),m_AreaSize.GetHeight()));
}

wxVertex2D wxChartCtrl::GetRealFromPoint(wxPoint point)
{
	wxVertex2D vertex;
	//todo: detect 0 div
	vertex.x = point.x/(m_Prescale.x * PRESCALER_CONST);
	vertex.y = point.y/(m_Prescale.y * PRESCALER_CONST);

	return vertex;
}

void wxChartCtrl::FreeVertsIfNeeded(size_t index)
{
	wxCurveItem *data = GetCurve(index);

	if(data->CurveList.GetCount() <= m_MaxVerts)
		return;

	size_t toDelete = data->CurveList.GetCount() - m_MaxVerts;

	while(toDelete > 0)
	{
		data->CurveList.DeleteNode(data->CurveList.GetFirst());
		toDelete--;
	}
}

size_t wxChartCtrl::AppendNewCurve(size_t maxVerts)
{
	wxCurveItem * data = new wxCurveItem();
	return AppendCurve(data,maxVerts);
}

size_t wxChartCtrl::AppendCurve(wxCurveItem *data, size_t maxVerts)
{
	int index = m_Curves.GetCount();
	m_MaxVerts = maxVerts;
	data->CurveList.DeleteContents(true);
	m_Curves.Append(data);	
	data->UpdItems = 0;
	return index;
}

void wxChartCtrl::RemoveCurve(size_t index)
{
	wxwxCurvesListNode *node = m_Curves.Item(index);
	m_Curves.DeleteNode(node);
}

wxCurveItem* wxChartCtrl::GetCurve(size_t index)
{
	return  m_Curves.Item(index)->GetData();
}

void wxChartCtrl::AddendCurveData(size_t index, wxVertex2D &v)
{
	wxVertex2D *vertex = new wxVertex2D(v.x,v.y);
	wxCurveItem *data = m_Curves.Item(index)->GetData();
	data->CurveList.Append(vertex);
	data->UpdItems++;

	if(!(data->CurveList.GetCount() % 99))
		FreeVertsIfNeeded(index);

}

void wxChartCtrl::Clear()
{
	m_Curves.Clear();
}

void wxChartCtrl::ScrollCurveEnd_X(size_t index)
{
	wxCurveItem *data = GetCurve(index);
	wxVertex2D max;
	
	/*for(size_t i = 0; i < data->CurveList.GetCount();i++)
	{
		wxVertex2D vertex = *data->CurveList.Item(i)->GetData();
		if(i == 0)
			max.x = vertex.x;
		else if(max.x < vertex.x)
			max.x = vertex.x;
	}*/
	//simple
	size_t len = data->CurveList.GetCount();
	if(len > 0)
	{
	max = *data->CurveList.Item(len-1)->GetData();
	wxVertex2D area = GetRealAreaSize();
	m_Offset.x = max.x - area.x;
	}

}

void wxChartCtrl::UpdateChart(unsigned long updateTime)
{
	if(!updateTime)
		this->Refresh();
	else if(m_LastUpdateTime + updateTime < GetTickCount())
	{
		this->Refresh();
		m_LastUpdateTime = GetTickCount();
	}
}

void wxChartCtrl::ScrollChart(size_t index, unsigned long updateTime)
{	
	wxCurveItem *data = GetCurve(index);
	
	if(m_Refresh)
	{
		ScrollCurveEnd_X(index);
		this->Refresh();
		m_Refresh = false;
		return;
	}

	if(data->CurveList.GetCount() <= 1)
		this->Refresh();
	else if(m_LastUpdateTime + updateTime < GetTickCount())
	{
	ScrollCurveEnd_X(index);				

	size_t count = data->UpdItems;
	size_t index1 = data->CurveList.GetCount() - count;
	size_t index2 = data->CurveList.GetCount() - 1;
	size_t updItem = 0;
	wxVertex2D v1 = *data->CurveList.Item(index1)->GetData();
	wxVertex2D v2 = *data->CurveList.Item(index2)->GetData();
	
	wxPoint p1 = GetPrescPointInvOff(v1);
	wxPoint p2 = GetPrescPointInvOff(v2);

	if((p2.x - p1.x) < 1)
		return;

	for(int i = index1 ; i > 0 ; i--)
	{
		count++;
		wxVertex2D v3 = *data->CurveList.Item(i)->GetData();
		wxPoint p3 = GetPrescPointInvOff(v3);

		if(p1.x - p3.x > 1)
			break;
	}
		
	wxSize size = m_AreaSize - wxSize(2,2);
	wxPoint start = m_AreaStart + wxPoint(1,1);
	wxBitmap bmp;
	bmp.Create(m_AreaSize.x - 2,m_AreaSize.y - 2);
	wxMemoryDC memdc;
	memdc.SelectObject(bmp);
	
	wxClientDC dc (this);

	size_t offset = p2.x - p1.x;

	memdc.Blit(wxPoint(0,0),size,&dc,start);
	dc.SetBrush(GetBackgroundColour());
	dc.SetTextForeground(GetBackgroundColour());
	dc.DrawRectangle(m_AreaStart,m_AreaSize);

	dc.Blit(start,size,&memdc,wxPoint(offset,0));
	

	DrawAreaGrid(dc,m_AreaStart,m_AreaSize,m_Prescale,m_Offset,m_Unit);
	for(size_t i = 0; i <m_Curves.GetCount() ;i++)
		UpdateCurveDraw(i,count);	
	//wxLogDebug(_("Pos1: [%d,%d]"),p1.x,p1.y);
	//wxLogDebug(_("Pos2: [%d,%d]"),p2.x,p2.y);
	m_LastUpdateTime = GetTickCount();
	}
}

void wxChartCtrl::SetCurvePen(size_t index,const wxPen &pen)
{
	GetCurve(index)->CurvePen = pen;
}

void wxChartCtrl::SetXLabel(wxString label)
{
	m_Style |= wxSHOW_X_LABEL;
	m_XLabel = label;
}

void wxChartCtrl::SetYLabel(wxString label)
{
	m_Style |= wxSHOW_Y_LABEL;
	m_YLabel = label;
}

void wxChartCtrl::SetChartStyle(long style)
{
	m_Style = style;
}

void wxChartCtrl::ShowUnits(bool units)
{
	if(units)
	{
		m_Style |= wxSHOW_X_UNITS;
		m_Style |= wxSHOW_Y_UNITS;
	}
	else
	{
		m_Style &= ~wxSHOW_X_UNITS;
		m_Style &= ~wxSHOW_Y_UNITS;
	}
}

void wxChartCtrl::ShowLabels(bool labels)
{
	if(labels)
	{
		m_Style |= wxSHOW_X_LABEL;
		m_Style |= wxSHOW_Y_LABEL;
	}
	else
	{
		m_Style &= ~wxSHOW_X_LABEL;
		m_Style &= ~wxSHOW_Y_LABEL;
	}
}

void wxChartCtrl::SetUnits(double xx, double yy)
{
	m_Unit.x = xx;
	m_Unit.y = yy;
}

void wxChartCtrl::SetChartXYUnits(double xx, double yy)
{
	wxVertex2D v,p;
	v.x = xx * m_Unit.x;
	v.y = yy * m_Unit.y;

	m_Prescale.x = m_AreaSize.x / (v.x* 100) ;
	m_Prescale.y = m_AreaSize.y / (v.y* 100);
}

void wxChartCtrl::SetOffset(double xx, double yy)
{
	m_Offset.x = xx;
	m_Offset.y = yy;
}

